package com.cait.title.dao;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

public abstract class AbstractDao<T>
{
    private Class<T> persistentClass;

    protected AbstractDao(Class<T> persistentClass)
    {
        this.persistentClass = persistentClass;
    }

    public void save(T item)
    {
        PersistenceManager pm = PMF.getPM();
        try
        {
            pm.makePersistent(item);
        } finally
        {
            pm.close();
        }
    }

    public void saveAll(List<T> items)
    {
        PersistenceManager pm = PMF.getPM();
        try
        {
            pm.makePersistentAll(items);
        } finally
        {
            pm.close();
        }
    }

    public void delete(T item)
    {
        PersistenceManager pm = PMF.getPM();
        try
        {
            pm.makePersistent(item);
            pm.deletePersistent(item);
        } finally
        {
            pm.close();
        }
    }

    public void deleteAll(List<T> items)
    {
        PersistenceManager pm = PMF.getPM();
        try
        {
            pm.makePersistentAll(items);
            pm.deletePersistentAll(items);
        } finally
        {
            pm.close();
        }
    }

    public T findById(Long id)
    {
        T ret = null;
        PersistenceManager pm = PMF.getPM();
        try
        {
            ret = pm.getObjectById(persistentClass, id);
            ret = pm.detachCopy(ret);
        } finally
        {
            pm.close();
        }
        return ret;
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll()
    {
        List<T> ret = null;
        PersistenceManager pm = PMF.getPM();
        try
        {
            ret = (List<T>) pm.newQuery(persistentClass).execute();
            ret = (List<T>) pm.detachCopyAll(ret);
        } finally
        {
            pm.close();
        }
        return ret;
    }

    public T findFirstByPropertyNameValue(String propertyName,
            Object propertyValue) throws Exception
    {
        return getFirst(findByPropertyNameValue(propertyName, propertyValue));
    }

    public List<T> findByPropertyNameValue(String propertyName,
            Object propertyValue) throws Exception
    {
        Property property = createProperty(propertyName, propertyValue);
        return findByProperties(Arrays.asList(property));
    }

    public T findFirstByPropertyName(T item, String propertyName)
            throws Exception
    {
        return getFirst(findByPropertyName(item, propertyName));
    }

    public T findFirstByPropertyNames(T item, List<String> propertyNames)
            throws Exception
    {
        return getFirst(findByPropertyNames(item, propertyNames));
    }

    public List<T> findByPropertyName(T item, String propertyName)
            throws Exception
    {
        return findByPropertyNames(item, Arrays.asList(propertyName));
    }

    public List<T> findByPropertyNames(T item, List<String> propertyNames)
            throws Exception
    {
        List<Property> properties = extractProperties(item, propertyNames);
        List<T> ret = findByProperties(properties);
        return ret;
    }

    public T getFirst(List<T> items)
    {
        T ret = null;
        if (items != null && items.isEmpty() == false)
        {
            ret = items.get(0);
        }
        return ret;
    }

    private Property createProperty(String name, Object value) throws Exception
    {
        Property ret = new Property();
        ret.setName(name);
        ret.setValue(value);
        if (value != null)
        {
            ret.setValueType(value.getClass());
        }
        return ret;
    }

    private List<Property> extractProperties(T item, List<String> propertyNames)
            throws Exception
    {
        List<Property> ret = new ArrayList<Property>();
        for (String propertyName : propertyNames)
        {
            ret.add(extractProperty(item, propertyName));
        }
        return ret;
    }

    private Property extractProperty(T item, String propertyName)
            throws Exception
    {
        Property ret = new Property();
        ret.setName(propertyName);
        Method getMethod = findGetMethod(item, propertyName);
        Class<?> valueType = getMethod.getReturnType();
        ret.setValueType(valueType);
        Object value = getMethod.invoke(item);
        ret.setValue(value);
        return ret;
    }

    @SuppressWarnings("unchecked")
    private List<T> findByProperties(List<Property> properties)
            throws Exception
    {
        List<T> ret = new ArrayList<T>();
        PersistenceManager pm = PMF.getPM();
        try
        {
            Query q = pm.newQuery(persistentClass);
            List<Object> values = new ArrayList<Object>();
            for (Property property : properties)
            {
                appendPropertyFilter(q, property);
                values.add(property.getValue());
            }
            ret = (List<T>) q.execute(values);
            pm.detachCopyAll(ret);
        } finally
        {
            pm.close();
        }
        return ret;
    }

    private void appendPropertyFilter(Query q, Property property)
            throws Exception
    {
        String fieldParam = property.getName() + "Param";
        q.setFilter(property.getName() + " == " + fieldParam);
        Class<?> valueType = String.class;
        if (property.getValueType() != null)
        {
            valueType = property.getValueType();
        }
        q.declareParameters(valueType.getSimpleName() + " " + fieldParam);
    }

    private Method findGetMethod(T item, String propertyName) throws Exception
    {
        String getMethodName = "get"
                + formatPropertyNameForMethod(propertyName);
        Method get = item.getClass().getMethod(getMethodName);
        return get;
    }

    private String formatPropertyNameForMethod(String propertyName)
    {
        return propertyName.substring(0, 1).toUpperCase()
                + propertyName.substring(1);
    }
}
